package com.ruoyi.flow.common.tools.mediaconverter.converter.reflections;

import com.ruoyi.flow.common.tools.mediaconverter.converter.converter.Converter;
import com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.scanners.*;
import com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.util.ConfigurationBuilder;
import com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.vfs.Vfs;
import com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.util.Utils;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.regex.Pattern;

import static com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.ReflectionUtils.*;
import static com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.util.Utils.*;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterables.concat;
import static java.lang.String.format;

public class Reflections {
    public static final Logger LOGGER = LoggerFactory.getLogger(Reflections.class);

    protected final transient Configuration configuration;
    protected Store store;

    public Reflections(final Configuration configuration) {
        this.configuration = configuration;
        store = new Store(configuration);

        if (configuration.getScanners() != null && !configuration.getScanners().isEmpty()) {
            //inject to scanners
            for (Scanner scanner : configuration.getScanners()) {
                scanner.setConfiguration(configuration);
                scanner.setStore(store.getOrCreate(Utils.index(scanner.getClass())));
            }

            scan();
        }
    }

    /**
     * 通过包名获取该包下的相关类型。
     * @param packages
     */
    public Reflections(final Object... packages) {
        this(ConfigurationBuilder.build(packages));
    }

    //
    protected void scan() {
        if (configuration.getUrls() == null || configuration.getUrls().isEmpty()) {
            LOGGER.warn("given scan urls are empty. set urls in the configuration");
            return;
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("going to scan these urls:\n" + Joiner.on("\n").join(configuration.getUrls()));
        }

        long time = System.currentTimeMillis();
        int scannedUrls = 0;
        ExecutorService executorService = configuration.getExecutorService();
        List<Future<?>> futures = Lists.newArrayList();

        for (final URL url : configuration.getUrls()) {
            try {
                if (executorService != null) {
                    futures.add(executorService.submit(new Runnable() {
                        public void run() {
                            if (LOGGER.isDebugEnabled())
                                LOGGER.debug("[" + Thread.currentThread().toString() + "] scanning " + url);
                            scan(url);
                        }
                    }));
                } else {
                    scan(url);
                }
                scannedUrls++;
            } catch (ReflectionsException e) {
                if (LOGGER.isWarnEnabled())
                    LOGGER.warn("could not create Vfs.Dir from url. ignoring the exception and continuing", e);
            }
        }

        //todo use CompletionService
        if (executorService != null) {
            for (Future future : futures) {
                try {
                    future.get();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }

        time = System.currentTimeMillis() - time;

        //gracefully shutdown the parallel scanner executor service.
        if (executorService != null) {
            executorService.shutdown();
        }
        int keys = 0;
        int values = 0;
        for (String index : store.keySet()) {
            keys += store.get(index).keySet().size();
            values += store.get(index).size();
        }

        LOGGER.info(format("Reflections took %d ms to scan %d urls, producing %d keys and %d values %s",
                time, scannedUrls, keys, values,
                executorService != null && executorService instanceof ThreadPoolExecutor ?
                        format("[using %d cores]", ((ThreadPoolExecutor) executorService).getMaximumPoolSize()) : ""));
    }

    protected void scan(URL url) {
        Vfs.Dir dir = Vfs.fromURL(url);

        try {
            for (final Vfs.File file : dir.getFiles()) {
                // scan if inputs filter accepts file relative path or fqn
                Predicate<String> inputsFilter = configuration.getInputsFilter();
                String path = file.getRelativePath();
                String fqn = path.replace('/', '.');
                if (inputsFilter == null || inputsFilter.apply(path) || inputsFilter.apply(fqn)) {
                    Class classObject = null;
                    for (Scanner scanner : configuration.getScanners()) {
                        try {
                            if (scanner.acceptsInput(path) || scanner.acceptResult(fqn)) {
                                classObject = scanner.scan(file, classObject);
                            }
                        } catch (Exception e) {
                            if (LOGGER.isDebugEnabled())
                                LOGGER.debug("could not scan file " + file.getRelativePath() + " in url " + url.toExternalForm() + " with scanner " + scanner.getClass().getSimpleName(), e);
                        }
                    }
                }
            }
        } finally {
            dir.close();
        }
    }

    //query

    /**
     * 获取某个类的所有子类
     *
     * @param type
     * @param <T>
     * @return
     */
    public <T> Set<Class<? extends T>> getSubTypesOf(final Class<T> type) {
        return Sets.newHashSet(ReflectionUtils.<T>forNames(
                store.getAll(Utils.index(SubTypesScanner.class), Arrays.asList(type.getName())), loaders()));
    }

    /**
     * 获取给定class类型的类注解
     *
     * @param annotation {@code Class}
     * @return
     */
    public Set<Class<?>> getTypesAnnotatedWith(final Class<? extends Annotation> annotation) {
        return getTypesAnnotatedWith(annotation, false);
    }

    /**
     * 获取给定注解类class类型的类注解
     *
     * @param annotation
     * @param honorInherited
     * @return
     */
    public Set<Class<?>> getTypesAnnotatedWith(final Class<? extends Annotation> annotation, boolean honorInherited) {
        Iterable<String> annotated = store.get(Utils.index(TypeAnnotationsScanner.class), annotation.getName());
        Iterable<String> classes = getAllAnnotated(annotated, annotation.isAnnotationPresent(Inherited.class), honorInherited);
        return Sets.newHashSet(concat(forNames(annotated, loaders()), forNames(classes, loaders())));
    }

    /**
     * get types annotated with a given annotation, both classes and annotations, including annotation member values matching
     * <p>{@link Inherited} is honored according to given honorInherited
     * <p/>depends on TypeAnnotationsScanner configured
     */
    public Set<Class<?>> getTypesAnnotatedWith(final Annotation annotation, boolean honorInherited) {
        Iterable<String> annotated = store.get(Utils.index(TypeAnnotationsScanner.class), annotation.annotationType().getName());
        Iterable<Class<?>> filter = filter(forNames(annotated, loaders()), withAnnotation(annotation));
        Iterable<String> classes = getAllAnnotated(Utils.names(filter), annotation.annotationType().isAnnotationPresent(Inherited.class), honorInherited);
        return Sets.newHashSet(concat(filter, forNames(filter(classes, not(in(Sets.newHashSet(annotated)))), loaders())));
    }

    protected Iterable<String> getAllAnnotated(Iterable<String> annotated, boolean inherited, boolean honorInherited) {
        if (honorInherited) {
            if (inherited) {
                Iterable<String> subTypes = store.get(Utils.index(SubTypesScanner.class), filter(annotated, new Predicate<String>() {
                    public boolean apply(String input) {
                        final Class<?> type = forName(input, loaders());
                        return type != null && !type.isInterface();
                    }
                }));
                return concat(subTypes, store.getAll(Utils.index(SubTypesScanner.class), subTypes));
            } else {
                return annotated;
            }
        } else {
            Iterable<String> subTypes = concat(annotated, store.getAll(Utils.index(TypeAnnotationsScanner.class), annotated));
            return concat(subTypes, store.getAll(Utils.index(SubTypesScanner.class), subTypes));
        }
    }

    /**
     * 获取给定class类型的方法注解
     *
     * @param annotation
     * @return
     */
    public Set<Method> getMethodsAnnotatedWith(final Class<? extends Annotation> annotation) {
        Iterable<String> methods = store.get(Utils.index(MethodAnnotationsScanner.class), annotation.getName());
        return Utils.getMethodsFromDescriptors(methods, loaders());
    }

    /**
     * get methods with parameter types matching given {@code types}
     */
    public Set<Method> getMethodsMatchParams(Class<?>... types) {
        return getMethodsFromDescriptors(store.get(Utils.index(MethodParameterScanner.class), Utils.names(types).toString()), loaders());
    }

    /**
     * get methods with return type match given type
     */
    public Set<Method> getMethodsReturn(Class returnType) {
        return getMethodsFromDescriptors(store.get(Utils.index(MethodParameterScanner.class), Utils.names(returnType)), loaders());
    }

    /**
     * get methods with any parameter annotated with given annotation
     */
    public Set<Method> getMethodsWithAnyParamAnnotated(Class<? extends Annotation> annotation) {
        return getMethodsFromDescriptors(store.get(Utils.index(MethodParameterScanner.class), annotation.getName()), loaders());

    }

    /**
     * get methods with any parameter annotated with given annotation, including annotation member values matching
     */
    public Set<Method> getMethodsWithAnyParamAnnotated(Annotation annotation) {
        return filter(getMethodsWithAnyParamAnnotated(annotation.annotationType()), withAnyParameterAnnotation(annotation));
    }

    /**
     * get all constructors annotated with a given annotation
     * <p/>depends on MethodAnnotationsScanner configured
     */
    public Set<Constructor> getConstructorsAnnotatedWith(final Class<? extends Annotation> annotation) {
        Iterable<String> methods = store.get(Utils.index(MethodAnnotationsScanner.class), annotation.getName());
        return Utils.getConstructorsFromDescriptors(methods, loaders());
    }

    /**
     * get all constructors annotated with a given annotation, including annotation member values matching
     * <p/>depends on MethodAnnotationsScanner configured
     */
    public Set<Constructor> getConstructorsAnnotatedWith(final Annotation annotation) {
        return filter(getConstructorsAnnotatedWith(annotation.annotationType()), withAnnotation(annotation));
    }

    /**
     * get constructors with parameter types matching given {@code types}
     */
    public Set<Constructor> getConstructorsMatchParams(Class<?>... types) {
        return getConstructorsFromDescriptors(store.get(Utils.index(MethodParameterScanner.class), Utils.names(types).toString()), loaders());
    }

    /**
     * get constructors with any parameter annotated with given annotation
     */
    public Set<Constructor> getConstructorsWithAnyParamAnnotated(Class<? extends Annotation> annotation) {
        return getConstructorsFromDescriptors(store.get(Utils.index(MethodParameterScanner.class), annotation.getName()), loaders());
    }

    /**
     * get constructors with any parameter annotated with given annotation, including annotation member values matching
     */
    public Set<Constructor> getConstructorsWithAnyParamAnnotated(Annotation annotation) {
        return filter(getConstructorsWithAnyParamAnnotated(annotation.annotationType()), withAnyParameterAnnotation(annotation));
    }

    /**
     * get all fields annotated with a given annotation
     * <p/>depends on FieldAnnotationsScanner configured
     */
    public Set<Field> getFieldsAnnotatedWith(final Class<? extends Annotation> annotation) {
        final Set<Field> result = Sets.newHashSet();
        for (String annotated : store.get(Utils.index(FieldAnnotationsScanner.class), annotation.getName())) {
            result.add(Utils.getFieldFromString(annotated, loaders()));
        }
        return result;
    }

    /**
     * get all methods annotated with a given annotation, including annotation member values matching
     * <p/>depends on FieldAnnotationsScanner configured
     */
    public Set<Field> getFieldsAnnotatedWith(final Annotation annotation) {
        return filter(getFieldsAnnotatedWith(annotation.annotationType()), withAnnotation(annotation));
    }

    /**
     * get resources relative paths where simple name (key) matches given namePredicate
     * <p>depends on ResourcesScanner configured
     */
    public Set<String> getResources(final Predicate<String> namePredicate) {
        Iterable<String> resources = Iterables.filter(store.get(Utils.index(ResourcesScanner.class)).keySet(), namePredicate);
        return Sets.newHashSet(store.get(Utils.index(ResourcesScanner.class), resources));
    }

    /**
     * get resources relative paths where simple name (key) matches given regular expression
     * <p>depends on ResourcesScanner configured
     * <pre>Set<String> xmls = reflections.getResources(".*\\.xml");</pre>
     */
    public Set<String> getResources(final Pattern pattern) {
        return getResources(new Predicate<String>() {
            public boolean apply(String input) {
                return pattern.matcher(input).matches();
            }
        });
    }

    /**
     * get all types scanned. this is effectively similar to getting all subtypes of Object.
     * <p>depends on SubTypesScanner configured with {@code SubTypesScanner(false)}, otherwise {@code ReflectionsException} is thrown
     * <p><i>note using this might be a bad practice. it is better to get types matching some criteria,
     * such as {@link #getSubTypesOf(Class)} or {@link #getTypesAnnotatedWith(Class)}</i>
     *
     * @return Set of String, and not of Class, in order to avoid definition of all types in PermGen
     */
    public Set<String> getAllTypes() {
        Set<String> allTypes = Sets.newHashSet(store.getAll(Utils.index(SubTypesScanner.class), Object.class.getName()));
        if (allTypes.isEmpty()) {
            throw new ReflectionsException("Couldn't find subtypes of Object. " +
                    "Make sure SubTypesScanner initialized to include Object class - new SubTypesScanner(false)");
        }
        return allTypes;
    }

    /**
     * returns the {@link Store} used for storing and querying the metadata
     */
    public Store getStore() {
        return store;
    }

    /**
     * returns the {@link Configuration} object of this instance
     */
    public Configuration getConfiguration() {
        return configuration;
    }

    private ClassLoader[] loaders() {
        return configuration.getClassLoaders();
    }
    
    public static void main(String[] args) {
		Reflections reflections = new Reflections("com.*");
		Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Converter.class);
		if(classes.size()>0) {
			for(Class<?> clazz : classes) {
				System.out.println(clazz);
//				JsonIgnore converter = clazz.getAnnotation(JsonIgnore.class);
				
			}
		}
	}
}
